using System;

namespace Meta
{
	[Serializable]
	public class Fluid : CallInterfaceProcedure
	{
		static readonly SimpleProcedureType defaultType;

		static Fluid() {
			TypeVariable result = new TypeVariable(typeof(object), null);
			result.isWired = true;
			defaultType = new SimpleProcedureType(result, new object[0], null, 0, 0, -1, false, result);
		}

		object defaultValue;
		public object DefaultValue {
			get {
				return defaultValue;
			}
			set {
				defaultValue = value;
			}
		}

		protected virtual object SpecialDefaultValue {
			get {
				return DefaultValue;
			}
			set {
				DefaultValue = value;
			}
		}

		[ThreadStatic]
		public static BindingGroup bindingChain=null;
		public Fluid(object defaultValue)
		{
			this.defaultValue = defaultValue;
			this.setter = new FluidSetter(this);
			this.DeclaredType = defaultType;
		}

		public Fluid(object name, object defaultValue) : this(defaultValue) {
			this.name = name;
		}

		public object Value {
			get {
				BindingGroup bindings = bindingChain;
			
				while (bindings != null) {
					object[] bindingList = bindings.bindingList;
					for (int i=0; i< bindingList.Length-1; i+=2) {
						if (bindingList[i]==this)
							return bindingList[i+1];
					}
					bindings = bindings.next;
				}

				return SpecialDefaultValue;
			}

			set {
				BindingGroup bindings = bindingChain;
			
				while (bindings != null) {
					object[] bindingList = bindings.bindingList;
					for (int i=0; i< bindingList.Length-1; i+=2) {
						if (bindingList[i]==this) {
							bindingList[i+1] = value;
							return;
						}
					}
					bindings = bindings.next;
				}

				SpecialDefaultValue = value;
			}
		}

		public override object Call() {
			return Value;
		}


		public class BindingGroup {
			public BindingGroup next;
			public object[] bindingList;

			public BindingGroup(object[] bindingList, BindingGroup next) {
				this.bindingList = bindingList;
				this.next = next;
			}
		}

		public static object Bind(object[] bindingList, Thunk thunk) {
			BindingGroup saved = bindingChain;
			object result=null;
			try {
				bindingChain = new BindingGroup(bindingList, bindingChain);
				result = thunk();
			} finally {
				bindingChain = saved;
			}

			return result;
		}
		public static object Bind(object[] bindingList, Procedure thunk) {
			BindingGroup saved = bindingChain;
			object result=null;
			try {
				bindingChain = new BindingGroup(bindingList, bindingChain);
				result = thunk.Call();
			} finally {
				bindingChain = saved;
			}

			return result;
		}

		public class FluidSetter : CallInterfaceProcedure, IArglist {
			Fluid fluid;

			public FluidSetter(Fluid fluid) {
				this.fluid = fluid;
				this.name = "set-"+fluid.name;
			}

			object[] Arglist {
				get {
					return new object[1] {"new-value" };
				}
			}


			public override object Call(object newValue) {
				fluid.Value = newValue;
				return fluid;
			}
		}

	}

	class FileFluid : Fluid {
		public FileFluid(object defaultValue) : base(defaultValue) {}

		protected override object SpecialDefaultValue {
			get {
				FileToken currentFile = TopLevel.CurrentSourceFileToken;

				if (currentFile!=null && currentFile.fluidTable.ContainsKey(this))
					return currentFile.fluidTable[this];
				else
					return DefaultValue;
			}
			set {
				FileToken currentFile = TopLevel.CurrentSourceFileToken;

				if (currentFile!=null)
					currentFile.fluidTable[this] = value;
				else
					throw new Exception("Attempt to set value of per-file fluid when current source file is unset");
			}
		}

	}

	class NamespaceFluid : Fluid {
		public NamespaceFluid(object defaultValue) : base(defaultValue) {}

		protected override object SpecialDefaultValue {
			get {
				Namespace currentNamespace = Namespace.CurrentNamespace;

				if (currentNamespace.fluidTable.ContainsKey(this))
					return currentNamespace.fluidTable[this];
				else
					return DefaultValue;
			}
			set {
				Namespace currentNamespace = Namespace.CurrentNamespace;

				if (currentNamespace!=null)
					currentNamespace.fluidTable[this] = value;
				else
					throw new Exception("Attempt to set value of per-namespace fluid when current namespace is unset");
			}
		}
	}
}
